﻿using Nemerle;
using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;
using Nemerle2.Compiler;

using System;
using System.Console;
using System.Collections.Generic;
using System.Linq;

using Microsoft.Cci;

using ROList = System.Collections.ObjectModel.ReadOnlyCollection;

namespace MetadataReader.CCI
{
  internal class CciReferencedAssembly : IReferencedAssembly
  {
    internal  _cciAssembly   : IAssembly;
              _provider      : CciMetadataProvider;
    mutable   _allTypesArray : array[CciTypeInfo] = array[];
    mutable   _initialized   : bool;
    
    public this(assembly : IAssembly, provider : CciMetadataProvider)
    {
      _cciAssembly  = assembly;
      _provider     = provider;
      FullName      = UnitHelper.StrongName(assembly : IAssemblyReference);
      ImagePath     = assembly.AssemblyIdentity.Location;
      Flags         = assembly.Flags :> AssemblyNameFlags;
      
      _ = Changed;
    }
    
    internal CciAssembly : IAssembly { get { _cciAssembly } }

    //internal AddMessage( msg : Msg) : void
    //{
    //  Msgs ::= msg;
    //}

    public CustomAttributes  : ICustomAttributes
    {
      [Memoize]
      get
      {
        CciCustomAttributes(_provider, _cciAssembly.Attributes)
      }
    }

    mutable _topLevelTypes : array[ITypeInfo];
    mutable _macroTypes    : array[ITypeInfo];
    
    ProcessTypes() : void
    {
      when (_topLevelTypes == null)
      {
        def types = _allTypesArray.Where(t => t.DeclaringType == null);
        def isMacro(info : ITypeInfo) : bool
        {
          info.IsInterface && info.Name == "IMacro" && info.Namespace == ["Nemerle", "Compiler"]
        }
        def (macros, types) = types.Split(t => t.Implements.Any(t => isMacro(t.info)));
        _topLevelTypes = types.ToArray()  :> array[ITypeInfo] ;
        _macroTypes    = macros.ToArray() :> array[ITypeInfo] ;
      }
    }

    /// <summary>
    /// Sequence of namespace types
    /// </summary>
    public TopLevelTypes : Seq[ITypeInfo]
    {
      get { ProcessTypes(); _topLevelTypes }
    }

    public Macros : Seq[IMacroInfo]
    {
      [Memoize]
      get
      {
        ProcessTypes();
        
        def aggregate(ti, attrs) : list[string]
        {
          match (attrs)
          {
            | [attr] => attr.PositionalArguments.Head.ToString() :: ti.Namespace;
            | _ => assert(false, "The old vertion of macro assembly used. You must pupdate it to use this API.");
          }
        }
        
        def macros = _macroTypes.Select(mt => (mt, mt.CustomAttributes.CustomAttribute["Nemerle.Internal.MacroInfoAttribute"]))
                       .GroupBy(aggregate, (ti, attrs) => (ti, attrs.Head));
        macros.MapToArray(g => MacroInfo(g.Key.Head, g.Key.Tail, g.ToArray()))
      }
    }
    
    /// <summary>Sequence of all types</summary>
    public AllTypes          : Seq[ITypeInfo]    { get { _allTypesArray } }
    
    public LookupType(_name : string, _typeParamsCount : int) : ITypeInfo
    {
      throw NotImplementedException()
    }

    // TODO: тут надо бросать ObjectDisposedException или AssemblyNotInitializedExceprion,
    // с проверкой соответсвующего условия!!!
    //CustomAttributes  : ICustomAttributes { get; }
    //TopLevelTypes     : Seq[NTypeInfo]    { get; }
    //AllTypes          : Seq[NTypeInfo]    { get; }
    //Macros            : Seq[IMacroInfo]      { get; }
    
    public Msgs              : list[Msg]         { get; private set; }
    public ImagePath         : string            { get; private set; }
    public FullName          : string            { get; private set; }
    public Name              : string            { get { _cciAssembly.Name.Value } }
    public Flags             : AssemblyNameFlags { get; private set; }

    public event Changed : EventHandler;
    
    public Init() : void
    {
      unless (_initialized)
      {        
        _initialized = true;
        //WriteLine($"  $(Threading.Thread.CurrentThread.ManagedThreadId) $(Name)");
        _allTypesArray = _provider.AddTypeInfos(_cciAssembly.GetAllTypes(), this);
      }
    }
    
    public Namespaces : ReadOnlyArray[string * ReadOnlyArray[ITypeInfo] * ReadOnlyArray[IMacroInfo]]
    {
    	get
    	{
        def forEmpty(str, replasment) : string { if (string.IsNullOrEmpty(str)) replasment else str }
        def assembly = this;
        
        def types  = assembly.TopLevelTypes.Select(t => (forEmpty($<#..$(t.Namespace; ".")#>, "-"), t : object));
        def macros = assembly.Macros.Select(m =>        (forEmpty($<#..$(m.Namespace; ".")#>, "-"), m : object));
        def all = types.Union(macros);
        def namespaces = all.GroupBy((ns, _) => ns, ((_, elem)) => elem).OrderBy(g => g.Key);
        def namespaceMetadatas = namespaces.MapToList(
          fun (ns)
          {
            def types = ns.OfType.[ITypeInfo]();
            def macros = ns.OfType.[IMacroInfo]();
            assert2(types.Count() + macros.Count() == ns.Count());
            
            def filterType(type : ITypeInfo) : bool
            {
              type.Accessibility %&& (Accessibility.Public | Accessibility.Protected | Accessibility.ProtectedOrInternal)
              && !type.Name.StartsWith("_N_")
            }
            
            (ns.Key, ReadOnlyArray(types.Where(filterType)), ReadOnlyArray(macros));
          });
          
        ReadOnlyArray(namespaceMetadatas)
    	}
    }
    
    public override ToString() : string
    {
    	$"Assembly: $Name"
    }
  }
}
